﻿#include "udp.h"
#include "ipv4.h"
#include "protocol.h"
#include "tools.h"
#include "routetbl.h"

#if DBG_DISP_ENABLED(DBG_UDP_LEVEL)
static display_udp_pkt(udp_pkt_t *pkt)
{
    plat_printf("----------------udp_pkt----------------\n");
    plat_printf("sport:%d  dport:%d  total_len:%d  checksum:%d\n",
                pkt->hdr.src_port, pkt->hdr.dest_port, pkt->hdr.total_len, pkt->hdr.checksum);
    plat_printf("---------------------------------------\n");
}
#else
#define display_udp_pkt(pkt)
#endif

/**
 * @brief 通过udp协议发送数据包
 * @param dest_ip 目的ip地址
 * @param dport 目的端口
 * @param src_ip 源ip地址
 * @param sport 目的端口
 * @param buf 需要发送的数据包
 */
net_err_t udp_out(ipaddr_t *dest_ip, uint16_t dport, ipaddr_t *src_ip, uint16_t sport, pktbuf_t *buf)
{
    // 添加包头
    net_err_t err = pktbuf_add_header(buf, sizeof(udp_hdr_t), true);
    if (err < NET_ERR_OK)
    {
        DBG_ERROR(DBG_UDP_LEVEL, "add udp headr failed in udp out!");
        return err;
    }
    udp_pkt_t *pkt = (udp_pkt_t *)pktbuf_data(buf);
    pkt->hdr.src_port = x_htons(sport);
    pkt->hdr.dest_port = x_htons(dport);
    pkt->hdr.total_len = x_htons(buf->total_size);
    pkt->hdr.checksum = 0;

    // 计算校验和
    if (ipaddr_is_any(src_ip))
    {
        route_entry_t *route_entry = routetbl_find(dest_ip);
        if (!route_entry)
        {
            DBG_ERROR(DBG_DGRAM_LEVEL, "can not get local ipaddr in dgram sendto!");
            err = NET_ERR_UNREACH;
            return err;
        }
        src_ip = &route_entry->netif->ipaddr;
    }
    pkt->hdr.checksum = tools_checksum_peso(buf, dest_ip, src_ip, NET_PROTOCOL_UDP);
    display_udp_pkt(pkt);

    err = ipv4_out(NET_PROTOCOL_UDP, dest_ip, src_ip, buf);
    if (err < NET_ERR_OK)
    {
        DBG_ERROR(DBG_UDP_LEVEL, "give pkt from udp to ipv4 failed!");
        return err;
    }

    return NET_ERR_OK;
}

/**
 * @brief 判断udp数据包是否合法
 * @param pkt udp数据包
 * @param size 数据包长度
 */
bool udp_is_pkt_ok(udp_pkt_t *pkt, int size)
{
    if (size < sizeof(udp_hdr_t) || size < pkt->hdr.total_len)
    {
        DBG_WARNING(DBG_UDP_LEVEL, "the udp pkt breakdown!");
        return false;
    }

    return true;
}

/**
 * @brief udp处理收到的数据包
 * @param src 收到的数据包的源ip地址
 * @param dst 收到的数据包的目的ip地址
 */
net_err_t udp_in(pktbuf_t *buf, ipaddr_t *src, ipaddr_t *dst)
{
    // 查找对应端口是否符合，注意，收到的数据包带有ipv4的包头
    pktbuf_reset_acc(buf);
    int iphdr_size = ipv4_hdr_size((ipv4_pkt_t *)pktbuf_data(buf));
    net_err_t err = pktbuf_set_cont(buf, iphdr_size + sizeof(udp_hdr_t));
    if (err < NET_ERR_OK)
    {
        DBG_ERROR(DBG_UDP_LEVEL, "set buf cont failed in udp in!");
        return err;
    }
    udp_pkt_t *pkt = (udp_pkt_t *)(pktbuf_data(buf) + iphdr_size);
    uint16_t dst_port = x_ntohs(pkt->hdr.dest_port);
    uint16_t src_port = x_ntohs(pkt->hdr.src_port);
    dgram_t *dgram_soc = udp_find(dst, dst_port, src, src_port);
    if (!dgram_soc)
    {
        DBG_WARNING(DBG_UDP_LEVEL, "can not find suit dgram socket!");
        return NET_ERR_UNREACH;
    }

    // 判断数据包是否合法，检查校验和
    pktbuf_remove_header(buf, iphdr_size);
    pkt = (udp_pkt_t *)pktbuf_data(buf);
    pktbuf_reset_acc(buf);
    if (pkt->hdr.checksum)
    {
        if (tools_checksum_peso(buf, dst, src, NET_PROTOCOL_UDP))
        {
            DBG_WARNING(DBG_UDP_LEVEL, "the pkt checksum is error!");
            return NET_ERR_BROKEN;
        }
    }
    udp_pkt_ntoh(pkt);
    display_udp_pkt(pkt);

    // 放入到socket对应的接收队列中
    if (!udp_is_pkt_ok(pkt, buf->total_size))
    {
        DBG_WARNING(DBG_UDP_LEVEL, "the udp in udp in is error!");
        return NET_ERR_BROKEN;
    }

    pktbuf_remove_header(buf, sizeof(udp_hdr_t) - sizeof(udp_from_t));
    udp_from_t *from = (udp_from_t *)pktbuf_data(buf); // 为了后续需要，添加记录包来源的包头
    from->port = x_htons(src_port);
    ipaddr_copy(&from->ip, src);

    err = dgram_recvbuf_add(dgram_soc, buf);
    if (err < NET_ERR_OK)
    {
        DBG_WARNING(DBG_UDP_LEVEL, "add buf to socket recv list failed in udp in!");
        return err;
    }

    return NET_ERR_OK;
}

/**
 * @brief 查找符合类型的socket
 * @param src_ip socket源ip地址
 * @param sport socket源端口
 * @param dst_ip socket目的ip地址
 * @param dport socket目的端口
 */
dgram_t *udp_find(ipaddr_t *src_ip, uint16_t sport, ipaddr_t *dst_ip, uint16_t dport)
{
    return dgram_find(src_ip, sport, dst_ip, dport);
}

/**
 * @brief 转换收到的udp包的包头的大小端格式
 */
void udp_pkt_ntoh(udp_pkt_t *pkt)
{
    pkt->hdr.dest_port = x_ntohs(pkt->hdr.dest_port);
    pkt->hdr.src_port = x_ntohs(pkt->hdr.src_port);
    pkt->hdr.total_len = x_ntohs(pkt->hdr.total_len);
}